import path from 'path'
import fs from 'fs-extra'
import chunk from 'chunk'
import unzip from 'unzip'
import sha256File from 'sha256-file'
import npmview from 'npmview'
import fetch from 'cross-fetch'

const mirror = 'https://registry.npmmirror.com/-/binary/electron/' // 'https://registry.npmmirror.com/binary.html?path=electron/' // https://repo.huaweicloud.com/electron/

const SupportPlatforms = {
  'darwin-arm64': 'darwin-arm64',
  'darwin-x64': 'darwin-x64',
  'win32-ia32': 'win32-ia32',
  'win32-x64': 'win32-x64',
  'win32-arm64': 'win32-arm64',
  'linux-arm': 'linux-arm',
  'linux-arm64': 'linux-arm64',
  'linux-armv7l': 'linux-armv7l',
  'linux-ia32': 'linux-ia32',
  'linux-x64': 'linux-x64',
  'mas-x64': 'mas-x64',
  'mas-arm64': 'mas-arm64'
}

const SupportPlatformsValue = Object.values(SupportPlatforms)

export async function downloadInstalledSymbols(
  destDir,
  platforms = SupportPlatformsValue,
  chunkSize = 1
) {
  const allVersions = await allElectronVersions()
  console.debug('[downloadInstalledSymbols]', allVersions)
  const chunkedVersion = chunk(allVersions, chunkSize)
  return Promise.all(
    chunkedVersion.map((v) =>
      downloadSymbol(v, destDir, platforms).catch((reason) => console.error(reason))
    )
  )
}

export async function downloadSymbol(version, destDir, platforms) {
  if (!version) {
    throw new Error('versions must not be null!')
  }
  if (!platforms) {
    platforms = SupportPlatformsValue
  }
  return fetchSymbolInfo(version, platforms).then((verPlatsSymbols) => {
    Promise.all(
      verPlatsSymbols.map((info) => {
        return fetchSymbol(info.version, info.symbol, info.sha256, destDir)
      })
    )
  })
}

export async function fetchSymbolInfo(
  version: string,
  platforms: string[]
): Promise<
  {
    sha256: string
    symbol: string
    version: string
  }[]
> {
  const symbols = platforms.map((pt) => `electron-v${version}-${pt}-symbols.zip`)
  console.debug('[fetchSymbolInfo]', `${mirror}${version}/SHASUM256.txt`)
  return fetch(`${mirror}${version}/SHASUMS256.txt`).then(async (data: Response) => {
    const lines = (await data.text()).split('\n')
    console.debug('[fetchSymbolInfo]', lines)
    const shaAndSymbol = lines
      .filter((line) => symbols.some((symbol) => line.indexOf(symbol) !== -1))
      .map((line) => {
        const [sha256, symbol] = line.split('*')
        return { sha256, symbol, version }
      })
    return shaAndSymbol
  })
}

async function fetchSymbol(version, symbol, sha256, destDir) {
  const zipPath = path.resolve(destDir, symbol)
  if (fs.existsSync(zipPath)) {
    throw new Error(JSON.stringify({ errorno: 1, msg: 'already exist' }))
  }
  const zipUrl = `${mirror}/${version}/${symbol}`
  let ws = fs.createWriteStream(zipPath)
  const response = await fetch(zipUrl)
  await streamSyncify(
    response.body?.pipeTo(ws as unknown as WritableStream<Uint8Array<ArrayBufferLike>>)
  )

  const sha = sha256File(zipPath)
  if (sha !== sha256) {
    fs.removeSync(zipPath)
    throw new Error(JSON.stringify({ errorno: 2, msg: `sha256 not equal! ${sha} ${sha256}` }))
  }

  const rs = fs.createReadStream(zipPath)
  const unzipDir = path.resolve(destDir, symbol.replace(/.zip$/, ''))
  if (fs.existsSync(unzipDir)) {
    return unzipDir
  }
  fs.ensureDirSync(unzipDir)
  ws = fs.createWriteStream(unzipDir)
  rs.pipe(unzip.Parse()).pipe(ws)
  await streamSyncify(ws)
  return unzipDir
}

export function allElectronVersions() {
  return new Promise((resolve, reject) => {
    console.debug('allElectronVersions start')
    npmview('electron', (err, _, moduleInfo) => {
      if (err) return reject(err)
      console.debug('[installedElectronVersion]', moduleInfo.versions)
      return resolve(moduleInfo.versions)
    })
  })
}

function streamSyncify(stream) {
  return new Promise((resolve, reject) => {
    stream.on('close', resolve).on('error', reject)
  })
}
